/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 3.0 Module
 * -------------------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief FBO stencilbuffer tests.
 *//*--------------------------------------------------------------------*/

#include "es3fFboStencilbufferTests.hpp"
#include "es3fFboTestCase.hpp"
#include "es3fFboTestUtil.hpp"
#include "gluTextureUtil.hpp"
#include "tcuTextureUtil.hpp"
#include "sglrContextUtil.hpp"
#include "glwEnums.hpp"

namespace deqp
{
namespace gles3
{
namespace Functional
{

using std::string;
using tcu::Vec2;
using tcu::Vec3;
using tcu::Vec4;
using tcu::IVec2;
using tcu::IVec3;
using tcu::IVec4;
using tcu::UVec4;
using namespace FboTestUtil;

class BasicFboStencilCase : public FboTestCase
{
public:
	BasicFboStencilCase (Context& context, const char* name, const char* desc, deUint32 format, IVec2 size, bool useDepth)
		: FboTestCase	(context, name, desc)
		, m_format		(format)
		, m_size		(size)
		, m_useDepth	(useDepth)
	{
	}

protected:
	void preCheck (void)
	{
		checkFormatSupport(m_format);
	}

	void render (tcu::Surface& dst)
	{
		const deUint32			colorFormat		= GL_RGBA8;

		GradientShader			gradShader		(glu::TYPE_FLOAT_VEC4);
		FlatColorShader			flatShader		(glu::TYPE_FLOAT_VEC4);
		deUint32				flatShaderID	= getCurrentContext()->createProgram(&flatShader);
		deUint32				gradShaderID	= getCurrentContext()->createProgram(&gradShader);

		deUint32				fbo				= 0;
		deUint32				colorRbo		= 0;
		deUint32				depthStencilRbo	= 0;

		// Colorbuffer.
		glGenRenderbuffers(1, &colorRbo);
		glBindRenderbuffer(GL_RENDERBUFFER, colorRbo);
		glRenderbufferStorage(GL_RENDERBUFFER, colorFormat, m_size.x(), m_size.y());

		// Stencil (and depth) buffer.
		glGenRenderbuffers(1, &depthStencilRbo);
		glBindRenderbuffer(GL_RENDERBUFFER, depthStencilRbo);
		glRenderbufferStorage(GL_RENDERBUFFER, m_format, m_size.x(), m_size.y());

		// Framebuffer.
		glGenFramebuffers(1, &fbo);
		glBindFramebuffer(GL_FRAMEBUFFER, fbo);
		glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorRbo);
		glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, depthStencilRbo);
		if (m_useDepth)
			glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthStencilRbo);
		checkError();
		checkFramebufferStatus(GL_FRAMEBUFFER);

		glViewport(0, 0, m_size.x(), m_size.y());

		// Clear framebuffer.
		glClearBufferfv(GL_COLOR, 0, Vec4(0.0f).getPtr());
		glClearBufferfi(GL_DEPTH_STENCIL, 0, 1.0f, 0);

		// Render intersecting quads - increment stencil on depth pass
		glEnable(GL_DEPTH_TEST);
		glEnable(GL_STENCIL_TEST);
		glStencilFunc(GL_ALWAYS, 0, 0xffu);
		glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);

		flatShader.setColor(*getCurrentContext(), flatShaderID, Vec4(1.0f, 0.0f, 0.0f, 1.0f));
		sglr::drawQuad(*getCurrentContext(), flatShaderID, Vec3(-1.0f, -1.0f,  0.0f), Vec3(+1.0f, +1.0f,  0.0f));

		gradShader.setGradient(*getCurrentContext(), gradShaderID, Vec4(0.0f), Vec4(1.0f));
		sglr::drawQuad(*getCurrentContext(), gradShaderID, Vec3(-1.0f, -1.0f, -1.0f), Vec3(+1.0f, +1.0f, +1.0f));

		glDisable(GL_DEPTH_TEST);

		// Draw quad with stencil test (stencil == 1 or 2 depending on depth) - decrement on stencil failure
		glStencilFunc(GL_EQUAL, m_useDepth ? 2 : 1, 0xffu);
		glStencilOp(GL_DECR, GL_KEEP, GL_KEEP);

		flatShader.setColor(*getCurrentContext(), flatShaderID, Vec4(0.0f, 1.0f, 0.0f, 1.0));
		sglr::drawQuad(*getCurrentContext(), flatShaderID, Vec3(-0.5f, -0.5f, 0.0f), Vec3(+0.5f, +0.5f, 0.0f));

		// Draw quad with stencil test where stencil > 1 or 2 depending on depth buffer
		glStencilFunc(GL_GREATER, m_useDepth ? 1 : 2, 0xffu);

		flatShader.setColor(*getCurrentContext(), flatShaderID, Vec4(0.0f, 0.0f, 1.0f, 1.0f));
		sglr::drawQuad(*getCurrentContext(), flatShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(+1.0f, +1.0f, 0.0f));

		readPixels(dst, 0, 0, m_size.x(), m_size.y(), glu::mapGLInternalFormat(colorFormat), Vec4(1.0f), Vec4(0.0f));
	}

private:
	deUint32	m_format;
	IVec2		m_size;
	bool		m_useDepth;
};

class DepthStencilAttachCase : public FboTestCase
{
public:
	DepthStencilAttachCase (Context& context, const char* name, const char* desc, deUint32 attachDepth, deUint32 attachStencil)
		: FboTestCase		(context, name, desc)
		, m_attachDepth		(attachDepth)
		, m_attachStencil	(attachStencil)
	{
		DE_ASSERT(m_attachDepth == GL_DEPTH_ATTACHMENT || m_attachDepth == GL_DEPTH_STENCIL_ATTACHMENT || m_attachDepth == GL_NONE);
		DE_ASSERT(m_attachStencil == GL_STENCIL_ATTACHMENT || m_attachStencil == GL_NONE);
		DE_ASSERT(m_attachDepth != GL_DEPTH_STENCIL || m_attachStencil == GL_NONE);
	}

protected:
	void render (tcu::Surface& dst)
	{
		const deUint32			colorFormat			= GL_RGBA8;
		const deUint32			depthStencilFormat	= GL_DEPTH24_STENCIL8;
		const int				width				= 128;
		const int				height				= 128;
		const bool				hasDepth			= (m_attachDepth == GL_DEPTH_STENCIL || m_attachDepth == GL_DEPTH_ATTACHMENT);
//		const bool				hasStencil			= (m_attachDepth == GL_DEPTH_STENCIL || m_attachStencil == GL_DEPTH_STENCIL_ATTACHMENT);

		GradientShader			gradShader			(glu::TYPE_FLOAT_VEC4);
		FlatColorShader			flatShader			(glu::TYPE_FLOAT_VEC4);
		deUint32				flatShaderID		= getCurrentContext()->createProgram(&flatShader);
		deUint32				gradShaderID		= getCurrentContext()->createProgram(&gradShader);

		deUint32				fbo					= 0;
		deUint32				colorRbo			= 0;
		deUint32				depthStencilRbo		= 0;

		// Colorbuffer.
		glGenRenderbuffers(1, &colorRbo);
		glBindRenderbuffer(GL_RENDERBUFFER, colorRbo);
		glRenderbufferStorage(GL_RENDERBUFFER, colorFormat, width, height);

		// Depth-stencil buffer.
		glGenRenderbuffers(1, &depthStencilRbo);
		glBindRenderbuffer(GL_RENDERBUFFER, depthStencilRbo);
		glRenderbufferStorage(GL_RENDERBUFFER, depthStencilFormat, width, height);

		// Framebuffer.
		glGenFramebuffers(1, &fbo);
		glBindFramebuffer(GL_FRAMEBUFFER, fbo);
		glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorRbo);

		if (m_attachDepth != GL_NONE)
			glFramebufferRenderbuffer(GL_FRAMEBUFFER, m_attachDepth, GL_RENDERBUFFER, depthStencilRbo);
		if (m_attachStencil != GL_NONE)
			glFramebufferRenderbuffer(GL_FRAMEBUFFER, m_attachStencil, GL_RENDERBUFFER, depthStencilRbo);

		checkError();
		checkFramebufferStatus(GL_FRAMEBUFFER);

		glViewport(0, 0, width, height);

		// Clear framebuffer.
		glClearBufferfv(GL_COLOR, 0, Vec4(0.0f).getPtr());
		glClearBufferfi(GL_DEPTH_STENCIL, 0, 1.0f, 0);

		// Render intersecting quads - increment stencil on depth pass
		glEnable(GL_DEPTH_TEST);
		glEnable(GL_STENCIL_TEST);
		glStencilFunc(GL_ALWAYS, 0, 0xffu);
		glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);

		flatShader.setColor(*getCurrentContext(), flatShaderID, Vec4(1.0f, 0.0f, 0.0f, 1.0f));
		sglr::drawQuad(*getCurrentContext(), flatShaderID, Vec3(-1.0f, -1.0f,  0.0f), Vec3(+1.0f, +1.0f,  0.0f));

		gradShader.setGradient(*getCurrentContext(), gradShaderID, Vec4(0.0f), Vec4(1.0f));
		sglr::drawQuad(*getCurrentContext(), gradShaderID, Vec3(-1.0f, -1.0f, -1.0f), Vec3(+1.0f, +1.0f, +1.0f));

		glDisable(GL_DEPTH_TEST);

		// Draw quad with stencil test (stencil == 1 or 2 depending on depth) - decrement on stencil failure
		glStencilFunc(GL_EQUAL, hasDepth ? 2 : 1, 0xffu);
		glStencilOp(GL_DECR, GL_KEEP, GL_KEEP);

		flatShader.setColor(*getCurrentContext(), flatShaderID, Vec4(0.0f, 1.0f, 0.0f, 1.0));
		sglr::drawQuad(*getCurrentContext(), flatShaderID, Vec3(-0.5f, -0.5f, 0.0f), Vec3(+0.5f, +0.5f, 0.0f));

		// Draw quad with stencil test where stencil > 1 or 2 depending on depth buffer
		glStencilFunc(GL_GREATER, hasDepth ? 1 : 2, 0xffu);

		flatShader.setColor(*getCurrentContext(), flatShaderID, Vec4(0.0f, 0.0f, 1.0f, 1.0f));
		sglr::drawQuad(*getCurrentContext(), flatShaderID, Vec3(-1.0f, -1.0f, 0.0f), Vec3(+1.0f, +1.0f, 0.0f));

		readPixels(dst, 0, 0, width, height, glu::mapGLInternalFormat(colorFormat), Vec4(1.0f), Vec4(0.0f));
	}

private:
	deUint32		m_attachDepth;
	deUint32		m_attachStencil;
};

FboStencilTests::FboStencilTests (Context& context)
	: TestCaseGroup(context, "stencil", "FBO Stencilbuffer tests")
{
}

FboStencilTests::~FboStencilTests (void)
{
}

void FboStencilTests::init (void)
{
	static const deUint32 stencilFormats[] =
	{
		GL_DEPTH32F_STENCIL8,
		GL_DEPTH24_STENCIL8,
		GL_STENCIL_INDEX8
	};

	// .basic
	{
		tcu::TestCaseGroup* basicGroup = new tcu::TestCaseGroup(m_testCtx, "basic", "Basic stencil tests");
		addChild(basicGroup);

		for (int fmtNdx = 0; fmtNdx < DE_LENGTH_OF_ARRAY(stencilFormats); fmtNdx++)
		{
			deUint32			format		= stencilFormats[fmtNdx];
			tcu::TextureFormat	texFmt		= glu::mapGLInternalFormat(format);

			basicGroup->addChild(new BasicFboStencilCase(m_context, getFormatName(format), "", format, IVec2(111, 132), false));

			if (texFmt.order == tcu::TextureFormat::DS)
				basicGroup->addChild(new BasicFboStencilCase(m_context, (string(getFormatName(format)) + "_depth").c_str(), "", format, IVec2(111, 132), true));
		}
	}

	// .attach
	{
		tcu::TestCaseGroup* attachGroup = new tcu::TestCaseGroup(m_testCtx, "attach", "Attaching depth stencil");
		addChild(attachGroup);

		attachGroup->addChild(new DepthStencilAttachCase(m_context, "depth_only",				"Only depth part of depth-stencil RBO attached",			GL_DEPTH_ATTACHMENT,			GL_NONE));
		attachGroup->addChild(new DepthStencilAttachCase(m_context, "stencil_only",				"Only stencil part of depth-stencil RBO attached",			GL_NONE,						GL_STENCIL_ATTACHMENT));
		attachGroup->addChild(new DepthStencilAttachCase(m_context, "depth_stencil_separate",	"Depth and stencil attached separately",					GL_DEPTH_ATTACHMENT,			GL_STENCIL_ATTACHMENT));
		attachGroup->addChild(new DepthStencilAttachCase(m_context, "depth_stencil_attachment",	"Depth and stencil attached with DEPTH_STENCIL_ATTACHMENT",	GL_DEPTH_STENCIL_ATTACHMENT,	GL_NONE));
	}
}

} // Functional
} // gles3
} // deqp
